package ui.xmlVisualization;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.filechooser.FileNameExtensionFilter;

import org.apache.commons.collections.Predicate;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.Text;
import org.dom4j.io.SAXReader;

import preprocess.XMLContracter;

import edu.uci.ics.jung.graph.ArchetypeEdge;
import edu.uci.ics.jung.graph.ArchetypeVertex;
import edu.uci.ics.jung.graph.Vertex;
import edu.uci.ics.jung.graph.decorators.DefaultToolTipFunction;
import edu.uci.ics.jung.graph.decorators.EdgeShape;
import edu.uci.ics.jung.graph.decorators.EdgeStringer;
import edu.uci.ics.jung.graph.decorators.VertexPaintFunction;
import edu.uci.ics.jung.graph.decorators.VertexShapeFunction;
import edu.uci.ics.jung.graph.decorators.VertexStringer;
import edu.uci.ics.jung.graph.impl.SparseTree;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.PluggableRenderer;
import edu.uci.ics.jung.visualization.ShapePickSupport;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.contrib.TreeLayout;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;

import static preprocess.XMLContracter.*;

/**
 * a class to visualize the xml document
 * 
 * @author huangcd
 * 
 */
public class XMLTree extends SparseTree implements VertexStringer,
        EdgeStringer, VertexPaintFunction, VertexShapeFunction, Predicate
{
    public final static Color[]             GRADIENT_COLOR       = {
            new Color(24, 97, 254), // new Color(20, 131, 246),
            new Color(24, 160, 246), // new Color(30, 210, 245),
            new Color(36, 241, 234), // new Color(67, 255, 200),
            new Color(106, 251, 176), // new Color(139, 255, 121),
            new Color(178, 248, 100), // new Color(205, 238, 73),
            new Color(221, 232, 44), // new Color(254, 216, 29),
            new Color(252, 174, 16), // new Color(255, 132, 26),
            new Color(251, 82, 13), // new Color(244, 55, 13),
            new Color(222, 42, 9), // new Color(196, 30, 18),
                                                                 };

    public static Dimension                 defalutSize          = new Dimension(
                                                                         800,
                                                                         600);
    public static Paint                     defaultDrawPaint     = Color.black;
    public static int                       defaultMaxVertexSize = 20;

    private static XMLVertex                vertex;
    private static XMLEdge                  edge;

    private static HashMap<Node, XMLVertex> vertexMap;
    boolean                                 showVertexLabel      = false;
    boolean                                 showEdgeLabel        = false;
    boolean                                 showAttribute        = true;
    boolean                                 showEdge             = true;
    VisualizationViewer                     vv;

    /**
     * convert a Node in a XML document to a XMLVertex
     * 
     * @param node
     *            a Node to convert
     * @param level
     *            the height of the node
     * @return
     */
    public static XMLVertex elementToXMLVertex(Node node, int level)
    {
        vertex = new XMLVertex(node);
        vertex.setLevel(level);

        // set shape of the vertex
        int base = (int) (36 / Math.log(level + 2));
        if (node instanceof Element)
        {
            Ellipse2D shape = new Ellipse2D.Double(-base / 2, -base / 2, base,
                    base);
            vertex.setShape(shape);
        }
        else if (node instanceof Text)
        {
            Rectangle2D shape = new Rectangle2D.Double(-base / 2, -base / 2,
                    base, base);
            vertex.setShape(shape);
        }
        else
        {
            int[] x = { -base / 2, 0, base / 2, 0 };
            int[] y = { 0, base / 2, 0, -base / 2 };
            Polygon shape = new Polygon(x, y, 4);
            vertex.setShape(shape);
        }

        // set color of this vertex
        int len = GRADIENT_COLOR.length;
        vertex.setFillPaint(GRADIENT_COLOR[level < len ? level : len - 1]);
        vertex.setDrawPaint(defaultDrawPaint);

        // save this mapping
        if (vertexMap == null)
        {
            vertexMap = new HashMap<Node, XMLVertex>();
        }
        vertexMap.put(node, vertex);
        return vertex;
    }

    /**
     * create a XMLTree by a Document represent a XML
     * 
     * @param doc
     */
    private XMLTree(Document doc)
    {
        super(elementToXMLVertex(doc.getRootElement(), 0));
        loadChildNode(doc.getRootElement());
    }

    /*
     * add the children of the given element as vertexes and their relations as
     * edges to the XMLTree
     */
    private void loadChildNode(Element elem)
    {
        XMLVertex parent = vertexMap.get(elem);
        int level = parent.getLevel();
        for (int i = 0; i < elem.nodeCount(); i++)
        {
            Node node = elem.node(i);
            if (node instanceof Element)
            {
                Element child = (Element) node;
                vertex = elementToXMLVertex(child, level + 1);
                this.addVertex(vertex);
                edge = new XMLEdge(parent, vertex);
                this.addEdge(edge);
                loadChildNode(child);
            }
            else if (node instanceof Text && node.asXML().trim().length() > 0)
            {
                vertex = elementToXMLVertex(node, level + 1);
                this.addVertex(vertex);
                edge = new XMLEdge(parent, vertex);
                this.addEdge(edge);
            }
            else if (!(node instanceof Text))
            {
                vertex = elementToXMLVertex(node, level + 1);
                this.addVertex(vertex);
                edge = new XMLEdge(parent, vertex);
                this.addEdge(edge);
            }
        }
    }

    @Override
    public String getLabel(ArchetypeVertex v)
    {
        if (this.showVertexLabel)
            return ((XMLVertex) v).toString(isShowAttribute());
        else
            return "";
    }

    @Override
    public String getLabel(ArchetypeEdge e)
    {
        if (this.showEdgeLabel)
            return ((XMLEdge) e).toString();
        else
            return "";
    }

    @Override
    public Paint getDrawPaint(Vertex v)
    {
        return ((XMLVertex) v).getDrawPaint();
    }

    @Override
    public Paint getFillPaint(Vertex v)
    {
        return ((XMLVertex) v).getFillPaint();
    }

    @Override
    public Shape getShape(Vertex v)
    {
        return ((XMLVertex) v).getShape();
    }

    @Override
    public boolean evaluate(Object arg0)
    {
        return this.showEdge;
    }

    public boolean isShowAttribute()
    {
        return showAttribute;
    }

    public boolean isShowVertexLabel()
    {
        return showVertexLabel;
    }

    public boolean isShowEdgeLabel()
    {
        return showEdgeLabel;
    }

    public boolean isShowEdge()
    {
        return showEdge;
    }

    public void setShowVertexLabel(boolean showVertexLabel)
    {
        this.showVertexLabel = showVertexLabel;
    }

    public void setShowEdgeLabel(boolean showEdgeLabel)
    {
        this.showEdgeLabel = showEdgeLabel;
    }

    public void setShowEdge(boolean showEdge)
    {
        this.showEdge = showEdge;
    }

    public void setShowAttribute(boolean showAttribute)
    {
        this.showAttribute = showAttribute;
    }

    private VisualizationViewer getVisualizationViewer(Dimension size)
    {
        vv = this.getVisualizationViewer();
        vv.setSize(size);
        return vv;
    }

    private VisualizationViewer getVisualizationViewer()
    {
        if (vv == null)
        {
            PluggableRenderer pr = new PluggableRenderer();
            vv = new VisualizationViewer(new TreeLayout(this, 30, 40), pr);
            pr.setVertexStringer(this);
            pr.setEdgeStringer(this);
            pr.setVertexPaintFunction(this);
            pr.setVertexShapeFunction(this);
            pr.setEdgeIncludePredicate(this);
            vv.setPickSupport(new ShapePickSupport());
            pr.setEdgeShapeFunction(new EdgeShape.Line());
            vv.setBackground(Color.white);
            vv.setToolTipFunction(new DefaultToolTipFunction());
        }
        return vv;
    }

    // set scaler
    private static void setScaler(final JPanel controls,
            final VisualizationViewer vv)
    {
        final ScalingControl scaler = new CrossoverScalingControl();
        scaler.scale(vv, 1f, vv.getCenter());

        JButton plus = new JButton("+");
        plus.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                scaler.scale(vv, 1.1f, vv.getCenter());
            }
        });
        controls.add(plus);
        JButton minus = new JButton("-");
        minus.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                scaler.scale(vv, 1 / 1.1f, vv.getCenter());
            }
        });
        controls.add(minus);

    }

    // show or hide labels
    private static void setLabelTags(final JPanel controls,
            final VisualizationViewer vv, final XMLTree tree)
    {
        JCheckBox showVertex = new JCheckBox("Vertex Labels");
        showVertex.addItemListener(new ItemListener()
        {
            public void itemStateChanged(ItemEvent e)
            {
                boolean result = e.getStateChange() == ItemEvent.SELECTED;
                tree.setShowVertexLabel(result);
                vv.repaint();
            }
        });
        showVertex.setSelected(false);
        controls.add(showVertex);

        JCheckBox showAttributes = new JCheckBox("Attributes");
        showAttributes.addItemListener(new ItemListener()
        {
            public void itemStateChanged(ItemEvent e)
            {
                boolean result = e.getStateChange() == ItemEvent.SELECTED;
                tree.setShowAttribute(result);
                vv.repaint();
            }
        });
        showAttributes.setSelected(false);
        controls.add(showAttributes);

        JCheckBox showEdge = new JCheckBox("Edge Labels");
        showEdge.addItemListener(new ItemListener()
        {
            public void itemStateChanged(ItemEvent e)
            {
                boolean result = e.getStateChange() == ItemEvent.SELECTED;
                tree.setShowEdgeLabel(result);
                vv.repaint();
            }
        });
        showEdge.setSelected(false);
        controls.add(showEdge);
    }

    // set save action
    private static void setSaveAction(final JPanel controls,
            final VisualizationViewer vv)
    {
        JButton saveButton = new JButton("Save");
        saveButton.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                BufferedImage bi = (BufferedImage) vv.createImage(
                        vv.getWidth(), vv.getHeight());

                JFileChooser chooser = new JFileChooser();
                FileNameExtensionFilter filter = new FileNameExtensionFilter(
                        "BMP图片", "bmp");
                chooser.setFileFilter(filter);
                int returnVal = chooser.showSaveDialog(null);
                if (returnVal == JFileChooser.APPROVE_OPTION)
                {
                    vv.paint(bi.getGraphics());
                    String name = chooser.getSelectedFile().getAbsolutePath();
                    if (!name.toLowerCase().endsWith(".bmp"))
                        name += ".bmp";
                    try
                    {
                        ImageIO.write(bi, "bmp", new File(name));
                    }
                    catch (IOException e1)
                    {
                        e1.printStackTrace();
                    }
                }
            }
        });
        controls.add(saveButton);

    }

    private static void setMouseController(final JPanel controls,
            final VisualizationViewer vv)

    {
        final ModalGraphMouse gm = new DefaultModalGraphMouse();
        vv.setGraphMouse(gm);
        gm.setMode(Mode.TRANSFORMING);
        controls.add(((DefaultModalGraphMouse) gm).getModeComboBox());
    }

    private static void setOpenAction(final JPanel controls,
            final VisualizationViewer vv, final File xml)

    {
        JButton showXMLButton = new JButton("open XML File...");
        showXMLButton.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    Desktop.getDesktop().open(xml);
                }
                catch (IOException e1)
                {
                    e1.printStackTrace();
                }
            }
        });
        controls.add(showXMLButton);
    }

    /**
     * return a JPanel contains both the graph and the controller
     * 
     * @param xmlFile
     * @return
     */
    public static JPanel generateJPanel(File xmlFile)
    {
        return generateJPanel(xmlFile, defalutSize);
    }

    /**
     * return a JPanel contains both the graph and the controller
     * 
     * @param xmlFile
     * @param size
     * @return
     */
    public static JPanel generateJPanel(File xmlFile, Dimension size)
    {
        try
        {
            final File xml = xmlFile;
            SAXReader reader = new SAXReader();
            Document doc = reader.read(xml);
            final XMLTree tree = new XMLTree(doc);
            final VisualizationViewer vv = tree.getVisualizationViewer(size);
            final JPanel jpanel = new JPanel();
            jpanel.setLayout(new BorderLayout());
            final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
            jpanel.add(panel, BorderLayout.CENTER);

            JPanel controls = new JPanel();

            // set scale action
            setScaler(controls, vv);
            // set label action
            setLabelTags(controls, vv, tree);
            // set save action
            setSaveAction(controls, vv);
            // set open action
            setOpenAction(controls, vv, xml);
            // set mouse controller
            setMouseController(controls, vv);

            jpanel.add(controls, BorderLayout.SOUTH);

            return jpanel;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * return a JPanel contains both the graph and the controller
     * 
     * @param doc
     * @return
     */
    public static JPanel generateJPanel(Document doc)
    {
        return generateJPanel(doc, defalutSize);
    }

    /**
     * return a JPanel contains both the graph and the controller
     * 
     * @param doc
     * @param size
     * @return
     */
    public static JPanel generateJPanel(Document doc, Dimension size)
    {
        try
        {
            final XMLTree tree = new XMLTree(doc);
            final VisualizationViewer vv = tree.getVisualizationViewer(size);
            final JPanel jpanel = new JPanel();
            jpanel.setLayout(new BorderLayout());
            final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
            jpanel.add(panel, BorderLayout.CENTER);

            JPanel controls = new JPanel();

            // set scale action
            // setScaler(controls, vv);
            // set label action=
            setLabelTags(controls, vv, tree);
            // set save action
            setSaveAction(controls, vv);
            // set mouse controller
            setMouseController(controls, vv);

            jpanel.add(controls, BorderLayout.SOUTH);

            return jpanel;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * show the xml document in a JFrame with the given size
     * 
     * @param xmlFile
     * @param size
     */
    public static void showTree(File xmlFile, Dimension size)
    {
        JFrame frame = new JFrame("XML Visualization");
        Container content = frame.getContentPane();
        frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
        JPanel panel = generateJPanel(xmlFile, size);
        content.add(panel);
        frame.setSize(size.width + 50, size.height + 40);
        // frame.pack();
        // frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    /**
     * show the xml document in a JFrame with the default size
     * 
     * @param xmlFile
     */
    public static void showTree(File xmlFile)
    {
        showTree(xmlFile, new Dimension(800, 600));
    }

    public static void showTree(Document doc, Dimension size)
    {
        JFrame frame = new JFrame("XML Visualization");
        Container content = frame.getContentPane();
        frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
        JPanel panel = generateJPanel(doc, size);
        content.add(panel);
        frame.setSize(size.width + 50, size.height + 40);
        frame.setLocationByPlatform(true);
        // frame.pack();
        frame.setVisible(true);
    }

    public static void showTree(Document doc)
    {
        showTree(doc, defalutSize);
    }

    /**
     * Main entry point. show how to use this class
     * 
     * @param args
     *            The first command line parameter specifies the filename
     */
    public static void main(String[] args) throws Exception
    {
        // String xmlPath;
        // // if (args.length != 0)
        // // xmlPath = args[0];
        // // else
        // // // xmlPath = "D:/INEX Dataset/test set/1480358/11182.xml";
        // // // xmlPath = "D:/INEX Dataset/test set/1474166/44635.xml";
        // xmlPath =
        // "d:/program files/eclipse/workspace/xmlTest/idrefBooks.xml";
        // final File xml = new File(xmlPath);
        // // SAXReader reader = new SAXReader();
        // // Document doc = reader.read(xml);
        // // //new XMLContracter().contract(doc, XMLContracter.ATTRIBUTE
        // // // | XMLContracter.NESTING | XMLContracter.REPETITION);
        // showTree(xml);
        // // showTree(doc);
        // SAXReader reader = new SAXReader();
        // Document doc = reader.read(new File("treeT1.xml"));
        // // showTree(doc);
        // XMLContracter contract = new XMLContracter();
        // // contract.contract(doc, XMLContracter.ATTRIBUTE |
        // // XMLContracter.NESTING
        // // | XMLContracter.REPETITION);
        // // showTree(doc);
        // doc = reader.read(new File(
        // "D:/INEX Dataset/temp/shakespeare/coriolan.xml"));
        // // contract.contract(doc, XMLContracter.ATTRIBUTE| XMLContracter.TEXT
        // // | XMLContracter.NESTING | XMLContracter.REPETITION);
        // showTree(doc);
        SAXReader reader = new SAXReader();
        Document doc = reader.read(new File(
                "D:/program files/Mozilla Firefox/blocklist.xml"));
        XMLContracter contract = new XMLContracter();
        contract.contract(doc, ATTRIBUTE|TEXT);
        showTree(doc);
    }
}
